﻿/// <reference path="jasmine.js" />
/// <reference path="../../SandScript/Context/sandScript.tools.js" />
/*globals jasmine, describe, it, expect, sandScript, spyOn, beforeEach, afterEach*/

describe('sandScript.tools', function () {
    'use strict';

    var tools = sandScript.tools;

    describe('objectTypeOf', function () {
        it('returns "Number" given NaN', function () {
            expect(tools.objectTypeOf(NaN)).toBe('Number');
        });

        it('returns "RegExp" given /[a-z]/', function () {
            expect(tools.objectTypeOf(/[a-z]/)).toBe('RegExp');
        });

        it('returns "Undefined" given undefined', function () {
            expect(tools.objectTypeOf(undefined)).toBe('Undefined');
        });

        it('returns "Null" given null', function () {
            expect(tools.objectTypeOf(null)).toBe('Null');
        });
    });

    describe('typeOf', function () {
        it('returns "boolean" given false', function () {
            expect(tools.typeOf(false)).toBe('boolean');
        });

        it('returns "boolean" given new Boolean()', function () {
            expect(tools.typeOf(new Boolean())).toBe('boolean'); //ignore jslint
        });

        it('returns "string" given "abcd"', function () {
            expect(tools.typeOf('abcd')).toBe('string');
        });

        it('returns "string" given new String()', function () {
            expect(tools.typeOf(new String())).toBe('string'); //ignore jslint
        });

        it('returns "number" given 123.456', function () {
            expect(tools.typeOf(123.456)).toBe('number');
        });

        it('returns "number" given new Number()', function () {
            expect(tools.typeOf(new Number())).toBe('number'); //ignore jslint
        });

        it('returns "nan" given NaN', function () {
            expect(tools.typeOf(NaN)).toBe('nan');
        });

        it('returns "nan" given parseInt("abcd", 0)', function () {
            expect(tools.typeOf(parseInt('abcd', 0))).toBe('nan');
        });

        it('returns "infinity" given Infinity', function () {
            expect(tools.typeOf(Infinity)).toBe('infinity');
        });

        it('returns "infinity" given 1.7976931348623157E+309', function () {
            expect(tools.typeOf(1.7976931348623157E+309)).toBe('infinity'); //ignore jslint
        });

        it('returns "infinity" given -1.7976931348623157E+309', function () {
            expect(tools.typeOf(-1.7976931348623157E+309)).toBe('infinity'); //ignore jslint
        });

        it('returns "date" given new Date()', function () {
            expect(tools.typeOf(new Date())).toBe('date');
        });

        it('returns "object" given {}', function () {
            expect(tools.typeOf({})).toBe('object');
        });

        it('returns "object" given new Object()', function () {
            expect(tools.typeOf(new Object())).toBe('object'); //ignore jslint
        });

        it('returns "object" given new MyConstructor()', function () {
            function MyConstructor() { }

            expect(tools.typeOf(new MyConstructor())).toBe('object');
        });

        it('returns "array" given []', function () {
            expect(tools.typeOf([])).toBe('array');
        });

        it('returns "array" given new Array()', function () {
            expect(tools.typeOf(new Array())).toBe('array'); //ignore jslint
        });

        it('returns "function" given function () { }', function () {
            expect(tools.typeOf(function () { })).toBe('function');
        });

        it('returns "function" given new Function()', function () {
            expect(tools.typeOf(new Function())).toBe('function'); //ignore jslint
        });

        it('returns "regexp" given /[a-z]/', function () {
            expect(tools.typeOf(/[a-z]/)).toBe('regexp');
        });

        it('returns "regexp" given new RegExp("[a-z]")', function () {
            expect(tools.typeOf(new RegExp('[a-z]'))).toBe('regexp');
        });

        it('returns "undefined" given undefined', function () {
            expect(tools.typeOf(undefined)).toBe('undefined');
        });

        it('returns "null" given null', function () {
            expect(tools.typeOf(null)).toBe('null');
        });
    });

    describe('isTypeOf', function () {
        it('calls typeOf passing through the value argument', function () {
            spyOn(tools, 'typeOf').andCallThrough();

            tools.isTypeOf('my value');

            expect(tools.typeOf).toHaveBeenCalledWith('my value');
        });

        it('compares the result of typeOf with a shortType if found', function () {
            spyOn(tools, 'typeOf').andCallFake(function () {
                return 'mytype';
            });

            var original = tools.shortType,
                actual;

            tools.shortType = {
                m: 'mytype'
            };

            actual = tools.isTypeOf('my value', 'm');

            expect(actual).toBeTruthy();

            tools.shortType = original;
        });

        it('compares the result of typeOf with the compare argument if not a shortType', function () {
            spyOn(tools, 'typeOf').andCallFake(function () {
                return 'my comparison';
            });

            var actual = tools.isTypeOf('my value', 'my comparison');

            expect(actual).toBeTruthy();
        });
    });

    describe('isObjectType', function () {
        it('returns true given {}', function () {
            expect(tools.isObjectType({})).toBeTruthy();
        });

        it('returns true given []', function () {
            expect(tools.isObjectType([])).toBeTruthy();
        });

        it('returns true given /[a-z]/', function () {
            expect(tools.isObjectType(/[a-z]/)).toBeTruthy();
        });

        it('returns true given function () { }', function () {
            expect(tools.isObjectType(function () { })).toBeTruthy();
        });

        it('returns true given new MyConstructor()', function () {
            function MyConstructor() { }

            expect(tools.isObjectType(new MyConstructor())).toBeTruthy();
        });

        it('returns true given new String()', function () {
            expect(tools.isObjectType(new String())).toBeTruthy(); //ignore jslint
        });

        it('returns false given "abcd"', function () {
            expect(tools.isObjectType('abcd')).toBeFalsy();
        });

        it('returns false given 123.456', function () {
            expect(tools.isObjectType(123.456)).toBeFalsy();
        });

        it('returns false given false', function () {
            expect(tools.isObjectType(false)).toBeFalsy();
        });
    });

    describe('isPrimitiveType', function () {
        it('returns true given "abcd"', function () {
            expect(tools.isPrimitiveType('abcd')).toBeTruthy();
        });

        it('returns true given 123.456', function () {
            expect(tools.isPrimitiveType(123.456)).toBeTruthy();
        });

        it('returns true given false', function () {
            expect(tools.isPrimitiveType(false)).toBeTruthy();
        });

        it('returns false given new String()', function () {
            expect(tools.isPrimitiveType(new String())).toBeFalsy(); //ignore jslint
        });

        it('returns false given {}', function () {
            expect(tools.isPrimitiveType({})).toBeFalsy();
        });

        it('returns false given []', function () {
            expect(tools.isPrimitiveType([])).toBeFalsy();
        });
    });

    describe('isDataDescriptor', function () {
        it('returns true given a generic field descriptor', function () {
            var container = {
                    genericField: 1234
                },
                actual;

            actual = Object.getOwnPropertyDescriptor(container, 'genericField');

            expect(tools.isDataDescriptor(actual)).toBeTruthy();
        });

        it('returns true given a data descriptor', function () {
            var container = {},
                actual;

            Object.defineProperty(container, 'dataProperty', {
                value: 1234
            });

            actual = Object.getOwnPropertyDescriptor(container, 'dataProperty');

            expect(tools.isDataDescriptor(actual)).toBeTruthy();
        });

        it('returns false given an accessor descriptor', function () {
            var container = {},
                actual;

            Object.defineProperty(container, 'accessorProperty', {
                get: function () {
                    return 1234;
                }
            });

            actual = Object.getOwnPropertyDescriptor(container, 'accessorProperty');

            expect(tools.isDataDescriptor(actual)).toBeFalsy();
        });
    });

    describe('constructFrom', function () {
        var ParentCtor,
            ChildCtor;

        beforeEach(function () {
            ParentCtor = jasmine.createSpy('ParentCtor');
            ChildCtor = function () {};
            ChildCtor.prototype = Object.create(ParentCtor.prototype);
        });

        it('throws when not given a function for the constructor parameter', function () {
            expect(function () {
                tools.constructFrom({}, 'not a function');
            }).toThrow('The constructor parameter must be a function.');
        });

        it('throws given a constructor not on the instance\'s prototype chain', function () {
            expect(function () {
                tools.constructFrom({}, function () { });
            }).toThrow('The given instance does not inherit from the constructor, or the constructor was used without the new keyword.');
        });

        it('calls parent constructor when it is on the instance\'s prototype chain', function () {
            var actual = new ChildCtor();

            tools.constructFrom(actual, ParentCtor);

            expect(ParentCtor).toHaveBeenCalled();
        });

        it('calls parent constructor with given parameters', function () {
            var actual = new ChildCtor();

            tools.constructFrom(actual, ParentCtor, ['first parameter', 2, true]);

            expect(ParentCtor).toHaveBeenCalledWith('first parameter', 2, true);
        });
    });

    describe('prototypeFrom', function () {
        var ParentCtor,
            ChildCtor;

        beforeEach(function () {
            ParentCtor = function () { };
            ChildCtor = function () { };
        });

        it('throws when not given a function for the descendant parameter', function () {
            expect(function () {
                tools.prototypeFrom('not a function');
            }).toThrow('The descendant parameter must be a function.');
        });

        it('throws when not given a function for the ancestor parameter', function () {
            expect(function () {
                tools.prototypeFrom(function () { }, 'not a function');
            }).toThrow('The ancestor parameter must be a function.');
        });

        it('calls Object.create, passing the descendant\'s prototype', function () {
            spyOn(Object, 'create').andCallFake(function () {
                return {};
            });

            tools.prototypeFrom(ChildCtor, ParentCtor);

            expect(Object.create).toHaveBeenCalledWith(ParentCtor.prototype);
        });

        it('adds ancestor prototype members to descendant', function () {
            ParentCtor.prototype.ancestorMethod = function () { };

            tools.prototypeFrom(ChildCtor, ParentCtor);
            var actual = new ChildCtor();

            expect(actual.ancestorMethod).toBeDefined();
        });

        it('resets prototype constructor to descendent', function () {
            tools.prototypeFrom(ChildCtor, ParentCtor);

            expect(ChildCtor.prototype.constructor).toBe(ChildCtor);
        });
    });
});